var __createBinding =
    (this && this.__createBinding) ||
    (Object.create
        ? (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              var desc = Object.getOwnPropertyDescriptor(m, k);
              if (
                  !desc ||
                  ("get" in desc
                      ? !m.__esModule
                      : desc.writable || desc.configurable)
              ) {
                  desc = { enumerable: true, get: () => m[k] };
              }
              Object.defineProperty(o, k2, desc);
          }
        : (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              o[k2] = m[k];
          });
var __setModuleDefault =
    (this && this.__setModuleDefault) ||
    (Object.create
        ? (o, v) => {
              Object.defineProperty(o, "default", {
                  enumerable: true,
                  value: v,
              });
          }
        : (o, v) => {
              o.default = v;
          });
var __importStar =
    (this && this.__importStar) ||
    ((mod) => {
        if (mod?.__esModule) return mod;
        var result = {};
        if (mod != null)
            for (var k in mod)
                if (k !== "default" && Object.hasOwn(mod, k))
                    __createBinding(result, mod, k);
        __setModuleDefault(result, mod);
        return result;
    });
var __importDefault =
    (this && this.__importDefault) ||
    ((mod) => (mod?.__esModule ? mod : { default: mod }));
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressionSimplifier = void 0;
const t = __importStar(require("@babel/types"));
const traverse_1 = __importDefault(require("@babel/traverse"));
const transformation_1 = require("../transformation");
const expression_1 = require("../../helpers/expression");
class ExpressionSimplifier extends transformation_1.Transformation {
    /**
     * Executes the transformation.
     * @param log The log function.
     */
    execute(_log) {
        const self = this;
        (0, traverse_1.default)(this.ast, {
            "UnaryExpression|BinaryExpression"(path) {
                const replacement = path.isUnaryExpression()
                    ? self.simplifyUnaryExpression(path.node)
                    : self.simplifyBinaryExpression(path.node);
                if (replacement) {
                    path.replaceWith(replacement);
                    self.setChanged();
                }
            },
        });
        return this.hasChanged();
    }
    /**
     * Attempts to simplify an expression.
     * @param expression The expression.
     * @returns The expression in the simplest form possible.
     */
    simplifyExpression(expression) {
        if (
            t.isUnaryExpression(expression) ||
            t.isBinaryExpression(expression)
        ) {
            const replacement = t.isUnaryExpression(expression)
                ? this.simplifyUnaryExpression(expression)
                : this.simplifyBinaryExpression(expression);
            return replacement || expression;
        } else {
            return expression;
        }
    }
    /**
     * Attempts to simplify a unary expression.
     * @param expression The unary expression.
     * @returns The simplified expression or undefined.
     */
    simplifyUnaryExpression(expression) {
        if (
            !ExpressionSimplifier.RESOLVABLE_UNARY_OPERATORS.has(
                expression.operator,
            )
        ) {
            return undefined;
        } else if ((0, expression_1.isNegativeNumericLiteral)(expression)) {
            return undefined; // avoid trying to simplify negative numbers
        }
        const argument = this.simplifyExpression(expression.argument);
        if (this.isResolvableExpression(argument)) {
            const argumentValue = this.getResolvableExpressionValue(argument);
            const value = this.applyUnaryOperation(
                expression.operator,
                argumentValue,
            );
            return this.convertValueToExpression(value);
        } else {
            return undefined;
        }
    }
    /**
     * Attempts to simplify a binary expression.
     * @param expression The binary expression.
     * @returns The simplified expression or undefined.
     */
    simplifyBinaryExpression(expression) {
        if (
            !t.isExpression(expression.left) ||
            !ExpressionSimplifier.RESOLVABLE_BINARY_OPERATORS.has(
                expression.operator,
            )
        ) {
            return undefined;
        }
        const left = this.simplifyExpression(expression.left);
        const right = this.simplifyExpression(expression.right);
        if (
            this.isResolvableExpression(left) &&
            this.isResolvableExpression(right)
        ) {
            const leftValue = this.getResolvableExpressionValue(left);
            const rightValue = this.getResolvableExpressionValue(right);
            const value = this.applyBinaryOperation(
                expression.operator,
                leftValue,
                rightValue,
            );
            return this.convertValueToExpression(value);
        } else if (
            expression.operator === "-" &&
            (0, expression_1.isNegativeNumericLiteral)(right)
        ) {
            // convert (- -a) to +a (as long as a is a number)
            expression.right = right.argument;
            expression.operator = "+";
            return expression;
        } else {
            return undefined;
        }
    }
    /**
     * Applies a unary operation.
     * @param operator The operator.
     * @param argument The argument value.
     * @returns The resultant value.
     */
    applyUnaryOperation(operator, argument) {
        switch (operator) {
            case "-":
                return -argument;
            case "+":
                return +argument;
            case "!":
                return !argument;
            case "~":
                return ~argument;
            case "typeof":
                return typeof argument;
            case "void":
                return void argument;
        }
    }
    /**
     * Applies a binary operation.
     * @param operator The resolvable binary operator.
     * @param left The value of the left expression.
     * @param right The value of the right expression.
     * @returns The resultant value.
     */
    applyBinaryOperation(operator, left, right) {
        switch (operator) {
            case "==":
                return left === right;
            case "!=":
                return left !== right;
            case "===":
                return left === right;
            case "!==":
                return left !== right;
            case "<":
                return left < right;
            case "<=":
                return left <= right;
            case ">":
                return left > right;
            case ">=":
                return left >= right;
            case "<<":
                return left << right;
            case ">>":
                return left >> right;
            case ">>>":
                return left >>> right;
            case "+":
                return left + right;
            case "-":
                return left - right;
            case "*":
                return left * right;
            case "/":
                return left / right;
            case "%":
                return left % right;
            case "**":
                return left ** right;
            case "|":
                return left | right;
            case "^":
                return left ^ right;
            case "&":
                return left & right;
        }
    }
    /**
     * Gets the real value from a resolvable expression.
     * @param expression The resolvable expression.
     * @returns The value.
     */
    getResolvableExpressionValue(expression) {
        switch (expression.type) {
            case "NumericLiteral":
            case "StringLiteral":
            case "BooleanLiteral":
            case "DecimalLiteral":
            case "BigIntLiteral":
                return expression.value;
            case "UnaryExpression":
                return -this.getResolvableExpressionValue(expression.argument);
            case "NullLiteral":
                return null;
            case "Identifier":
                return undefined;
            case "ArrayExpression":
                return [];
            case "ObjectExpression":
                return {};
        }
    }
    /**
     * Attempts to convert a value of unknown type to an expression node.
     * @param value The value.
     * @returns The expression or undefined.
     */
    convertValueToExpression(value) {
        switch (typeof value) {
            case "string":
                return t.stringLiteral(value);
            case "number":
                return value >= 0
                    ? t.numericLiteral(value)
                    : t.unaryExpression("-", t.numericLiteral(Math.abs(value)));
            case "boolean":
                return t.booleanLiteral(value);
            case "bigint":
                return t.bigIntLiteral(value.toString());
            case "undefined":
                return t.identifier("undefined");
            default:
                return undefined;
        }
    }
    /**
     * Returns whether a node is a resolvable expression that can be
     * evaluated safely.
     * @param node The AST node.
     * @returns Whether.
     */
    isResolvableExpression(node) {
        return (
            (t.isLiteral(node) &&
                !t.isRegExpLiteral(node) &&
                !t.isTemplateLiteral(node)) ||
            (t.isUnaryExpression(node) &&
                node.operator === "-" &&
                t.isLiteral(node.argument)) ||
            (t.isIdentifier(node) && node.name === "undefined") ||
            (t.isArrayExpression(node) && node.elements.length === 0) ||
            (t.isObjectExpression(node) && node.properties.length === 0)
        );
    }
}
exports.ExpressionSimplifier = ExpressionSimplifier;
ExpressionSimplifier.properties = {
    key: "expressionSimplification",
};
ExpressionSimplifier.RESOLVABLE_UNARY_OPERATORS = new Set([
    "-",
    "+",
    "!",
    "~",
    "typeof",
    "void",
]);
ExpressionSimplifier.RESOLVABLE_BINARY_OPERATORS = new Set([
    "==",
    "!=",
    "===",
    "!==",
    "<",
    "<=",
    ">",
    ">=",
    "<<",
    ">>",
    ">>>",
    "+",
    "-",
    "*",
    "/",
    "%",
    "**",
    "|",
    "^",
    "&",
]);
